package cn.zwx.multi_datasource_example.config;

import cn.zwx.multi_datasource_example.constants.DataSourceConstant;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.mysql.cj.jdbc.MysqlXADataSource;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.SystemException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
 * @description: 动态数据配置类
 * @projectName:multi_datasource_example
 * @see:cn.zwx.multi_datasource_example.config
 * @author:zhangwenxue
 * @createTime:2021/8/15 23:05
 * @version:1.0
 */
@Configuration
public class DynamicDataSourceConfig {


    public static final String basePackage ="spring.datasource.";

    @Autowired
    private MybatisProperties mybatisProperties;

    private static final String defaultMapperLocalPatch = "classpath*:mapper/*.xml";

    @Value("${spring.datasource.type:com.zaxxer.hikari.HikariDataSource}")
    String xaDataSourceClassName;


    @Primary
    @Bean(name = "db1DataSource")
    public DataSource db1DataSource(Environment env) throws SQLException {
        return this.getAtomikosDataSourceBean(env, DataSourceConstant.DB1);
    }


    @Bean(name = "db2DataSource")
    public DataSource db2DataSource(Environment env) throws SQLException {
        return this.getAtomikosDataSourceBean(env, DataSourceConstant.DB2);
    }

    /**
     * @description 初始化事务源
     * @return org.springframework.boot.jta.atomikos.AtomikosDataSourceBean
     * @author zhangwenxue
     * @createTime 2021/8/16 0:30
     **/
    private AtomikosDataSourceBean getAtomikosDataSourceBean(Environment env, String sourceName) throws SQLException {
        // 1.获取yaml配置信息
        Properties prop = build(env, basePackage + sourceName + ".");
        // 2.创建xa datasource
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setURL((String) prop.get("url"));
        mysqlXaDataSource.setUser((String) prop.get("username"));
        mysqlXaDataSource.setPassword((String) prop.get("password"));
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(Boolean.TRUE);
        // 3.注册到我们全局事务上(创建 Atomiko， 并将 mysql的XA交给JTA管理)
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        // 设置唯一资源名
        xaDataSource.setUniqueResourceName(sourceName);
        xaDataSource.setXaProperties(prop);
        return xaDataSource;
    }

    // 配置数据源
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("db1DataSource") DataSource db1DataSource,
                                        @Qualifier("db2DataSource") DataSource db2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceConstant.DB1, db1DataSource);
        targetDataSources.put(DataSourceConstant.DB2, db2DataSource);
        return new DynamicDataSource(db1DataSource, targetDataSources);
    }
    @Primary
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTransactionFactory(new MultiDataSourceTransactionFactory());
        // 不知道是不是多数据源的原因导致 mybatis无法感知mapper yaml配置 这里手动注入
        //原因:在MybatisAutoConfiguration  因为我们重写了 SqlSessionFactory spring就不会走myBaits的SqlSessionFactory
        // 所以需要我们手动去mybatis属性加载进去
        String[] mapperLocations = mybatisProperties.getMapperLocations();
        BeanUtils.copyProperties(mybatisProperties,bean);
        Resource[] resources = new Resource[]{};
        for (String mapperLocation : mapperLocations) {
            Resource[] resource = new PathMatchingResourcePatternResolver().getResources(mapperLocation);
            if (Objects.nonNull(resource)){
                resources = ArrayUtils.addAll(resource, resources);
            }
        }
        // 扫描指定目录的xml
        bean.setMapperLocations(resources);
        return bean.getObject();
    }
    @Bean(name="sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /**
     * 分布式事务使用JTA管理，不管有多少个数据源只要配置一个 JtaTransactionManager
     * @return
     */
    /*atomikos事务管理器*/
    private UserTransactionManager userTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    private UserTransactionImp userTransactionImp() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(5000);
        return userTransactionImp;
    }

    @Bean
    public JtaTransactionManager jtaTransactionManager() throws SystemException {
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setTransactionManager(userTransactionManager());
        jtaTransactionManager.setUserTransaction(userTransactionImp());
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }


    private Properties build(Environment env, String prefix) {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("driver-class-name", env.getProperty(prefix + "driver-class-name", ""));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", ""));
        prop.put("continueOnError", env.getProperty(prefix + "continueOnError", ""));
        prop.put("test-on-borrow", env.getProperty(prefix + "test-on-borrow", ""));
        prop.put("test-while-idle", env.getProperty(prefix + "test-while-idle", ""));
        prop.put("time-between-eviction-runs-millis", env.getProperty(prefix + "time-between-eviction-runs-millis", ""));
        prop.put("validation-query", env.getProperty(prefix + "validation-query", ""));
        return prop;
    }




}
